Skip to content

Merge the product tour into the homepage - #12

Merged
alexhking merged 3 commits into
mainfrom
merge-product-into-home
Aug 24, 2026
Merged

Merge the product tour into the homepage#12
alexhking merged 3 commits into
mainfrom
merge-product-into-home

Conversation

@alexhking

Copy link
Copy Markdown
Collaborator

The product tour was a page of its own that most visitors never reached, so the homepage described the product only in the abstract. This folds it into the root page and retires /product.

Page order is now: hero → video → prompt CTA → the four tour sections → compact benefits → manifesto → connect.

The tour

Section markup moves over unchanged inside a .product-page wrapper. That class is load-bearing: every callout-marker coordinate in style/product.css is a custom property scoped to it, so the whole stylesheet applies on the homepage untouched.

  • One prompt iframe per page. src/index.ts binds it by id, so the tour's closing CTA (and its content section in objects/product.toml) would have been a dead duplicate.
  • Hero tilt bound once. Both source pages registered their own mousemove handler for [data-tilt-hero]; merged naively that was two handlers writing conflicting transforms to the same element.
  • /product 301s to /#product via public/_redirects, so inbound links and the old nav target still land. Cloudflare Pages picks it up the same way it already picks up public/_headers.

Compact benefit row

All four kept, but 882px → 392px at desktop (1016 → 501 tablet, 1830 → 1023 mobile). Tilt and the glare layer are gone — at a quarter size the tilt read as jitter and the glare as a smudge. Scoped as a modifier because showcase.liquid still uses the full-size cards. Columns verified 4 / 2 / 1 at the 1101 / 641 breakpoints.

Nav

Product has no page of its own to match on, so it scroll-spies the tour instead — lit only while the tour crosses the viewport's centre band. Measured: on at scrollY ≈1400, off at ≈4100, exactly one on/off pair across the page.

It also takes over its own anchor clicks, as does the logo. Both were doing a full document reload: /#product and / are each a different document from /index.html, so the browser reloaded before landing. umami.js compounded it — for any <a data-umami-event> it calls preventDefault itself, awaits its tracking request, then assigns location.href, which no handler can cancel. Those two links now report their events directly and keep the navigation in-page. Verified: 0 reloads, movement starts immediately, eased scroll settles ~400ms, prefers-reduced-motion jumps instantly, modified clicks fall through, and 21 other tracked anchors are untouched.

Video slot

Sits under the hero, currently hidden behind show_poster_placeholder = false while the video is produced. Attaching a real video turns the section on regardless of that flag, so nothing needs remembering when it lands.

Claude Code CTA

Merged with origin/main's new in-page flow (Turnstile → claude-cli:// → modal) and restyled the trigger from a second primary-cta into the outlined treatment the manifesto link uses — two peer-weight buttons under one prompt box split the primary action. The shared box lifts onto a shared selector so both are one component; both render at 58px with identical border, radius, padding and font, and the manifesto link's icon and padding are unchanged. Still a <button> with the same id, so build-with-claude.ts binds it exactly as before.

Uses Anthropic's official Spark mark at 24px, left in its own clay rather than recoloured to the orange label — worth a second opinion if you'd rather it match.

Also fixed

A pre-existing horizontal scroll on the homepage: md:w-[1200px] on the manifesto CTA wrapper forced 1200px at any viewport ≥768px. No overflow now at 390/430/500/640/700/768/900/1024/1200/1440.

Verification note

Behaviour was verified by driving real Chrome over CDP rather than headless screenshots — headless under --virtual-time-budget does not deliver scroll-driven IntersectionObserver callbacks at all, which silently made the scroll-spy look broken when it wasn't.

https://claude.ai/code/session_01PJBsFWSPdZvjcWsFwJh587

The tour was a page of its own that most visitors never reached, so the
homepage described the product only in the abstract. Fold it into the root
page and retire /product.

The section markup moves over unchanged inside a .product-page wrapper: every
callout-marker coordinate in style/product.css is a custom property scoped to
that class, so the whole stylesheet applies on the homepage untouched.

- One prompt iframe per page. src/index.ts binds it by id, so the tour's
  closing CTA (and its content section) would have been a dead duplicate.
- Hero tilt is bound once. Both source pages registered their own mousemove
  handler for [data-tilt-hero]; merged naively that was two handlers writing
  conflicting transforms to the same element.
- The benefit row is compact: 882px -> 392px at desktop. Tilt and the glare
  layer are gone - at a quarter size the tilt read as jitter and the glare as
  a smudge. Scoped as a modifier so showcase.liquid keeps the full-size cards.
- Nav scroll-spies the tour instead of matching a page, and takes over its own
  anchor clicks. "/#product" and "/" are each a different document from
  "/index.html", so the browser reloaded the whole page before landing.
  umami.js compounded it: for any <a data-umami-event> it preventDefaults,
  awaits its tracking request, then assigns location.href itself. Those two
  links now report their events directly and keep the navigation in-page.
- A video slot sits under the hero, currently hidden behind a flag while the
  video is produced. Attaching a real video turns it on regardless of the flag.
- Fixes a pre-existing horizontal scroll: md:w-[1200px] on the manifesto CTA
  forced 1200px at any viewport >= 768px.

Claude-Session: https://claude.ai/code/session_01PJBsFWSPdZvjcWsFwJh587
Two peer-weight buttons under one prompt box split the primary action. The
prompt and Generate are the main path; the terminal route is the alternate,
so give it the outlined treatment the manifesto link already uses rather than
a second primary-cta.

The shared box - font, padding, hairline border, radius, centred fit-content,
hover brighten - lifts out of .manifesto-cta so both are the same component
rather than one copying the other's numbers. What stays specific to the
manifesto link is its block display and the padding-left that clears its
background-image icon; the launcher gets flex with a 1rem gap, which
reproduces the same icon-to-label spacing a different way.

Still a <button> with the same id: it opens a dialog rather than navigating,
and src/build-with-claude.ts binds it by id, so none of its behaviour moves.
The "or" separator only earns its place between two buttons, so it goes.

Uses Anthropic's official Spark mark at 24px to match icon-docs.svg, left in
its own clay rather than recoloured to the label.

Claude-Session: https://claude.ai/code/session_01PJBsFWSPdZvjcWsFwJh587
@alexhking
alexhking deployed to archival.dev August 24, 2026 22:39 — with GitHub Actions Active
@alexhking
alexhking merged commit dd903c8 into main Aug 24, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant